This notebook contains an implementation of the block SVD algorithm described in LAWN 283
In [264]:
using PyPlot
using PyCall
@pyimport matplotlib.patches as patches
function block(A,s1=5,s2=5)
m, n=size(A)
mi=[i:min(i+s1-1,m) for i=1:s1:m]
ni=[j:min(j+s2-1,n) for j=1:s2:n]
[A[i,j] for i in mi, j in ni]
end
unblock(A)=hvcat(tuple([size(A,2) for i=1:size(A,1)]...), A'...)
# Key BLAS3 block operator
# A few block apply Q's
import Base.LinAlg.QRPackedQ, Base.LinAlg.Ac_mul_B, Base.*
Ac_mul_B(Q::Base.LinAlg.QRPackedQ,B)=hcat([block(Q'*unblock(B[:,j])) for j in 1:size(B,2)]...)
*{T<:Matrix}(A::Array{T},Q::Base.LinAlg.QRPackedQ)=vcat([block(unblock(A[i,:])*Q) for i in 1:size(A,1)]...)
# A few block qrQ's
qrQ{T<:Number}(A::Array{T})=qrfact(A)[:Q]
qrQ{T<:Matrix}(A::Array{T})=qrQ(unblock(A))
qrQc{T<:Matrix}(A::Array{T})=qrQ(unblock(A)')
spyt{T<:Number}(A::Array{T})=spy(A, precision=sqrt(eps(T)), alpha=0.8)
spy{T<:Matrix}(A::Array{T})=spy(unblock(A), precision=sqrt(eps()), alpha=0.7) # Threshold for convenience
#Convenience drawing functions
rect_red(x0, y0, dx, dy)=gca()[:add_patch](patches.Rectangle((x0-1.5,y0-1.5),dx,dy,linewidth=5,facecolor="none",edgecolor="red"))
rect_blue(x0, y0, dx, dy)=gca()[:add_patch](patches.Rectangle((x0-1.5,y0-1.5),dx,dy,linewidth=3,facecolor="none",edgecolor="blue"))
rect_gray(x0, y0, dx, dy)=gca()[:add_patch](patches.Rectangle((x0-1.5,y0-1.5),dx,dy,linewidth=1,facecolor="none",edgecolor="#aaaaaa"))
rect_red(yr, xr)=rect_red(xr.start,yr.start,xr.len,yr.len)
rect_blue(yr, xr)=rect_blue(xr.start,yr.start,xr.len,yr.len)
rect_gray(yr, xr)=rect_gray(xr.start,yr.start,xr.len,yr.len)
Out[264]:
In [265]:
N=15
s=5; #block size
A=block(randn(N,N));
Q=qrQ(A[1,1])
A[1,:]=Q'*A[1,:]
spy(A)
rect_red(1:s,1:s) #QR from this block
rect_blue(1:s,1:N) #QR applied to this block
Out[265]:
In [266]:
for j=2:3
Q=qrQ(A[[1,j],1]) # There is a special QR for a "domino"=[◥;◼]
A[[1,j],:]=Q'*A[[1,j],:] # Q' acts like a square matrix even though Q was "thin"
rect_red(1,(j-1)*s+1,s,s)#QR from this block
end
spy(A)
Out[266]:
In [267]:
#Now do this from the right
Q=qrfact(A[1,2]')[:Q] # We really need an LQ
A[:,2]*=Q
spy(A)
rect_red(s+1,1,s,s) #QR from this block
rect_blue(1:s,s+1:N) #QR applied to this block
Out[267]:
In [268]:
for j=3:3
Q=qrQ(unblock(A[1,[2,j]])') # This is a special LQ for a "domino"=[◥;◼]'
A[:,[2,j]]*=Q
rect_red((j-1)*s+1,1,s,s)#QR from this block
end
spy(A)
Out[268]:
In [269]:
function BandBidiagonal(A::Array{Array{Float64,2},2})
(m,n)=size(A)
for i=1:n
Q=qrQ(A[i,i])
A[i,:]=Q'*A[i,:]
for j=(i+1):n
Q=qrQ(A[[i,j],i]) # QR for a "domino"=[◥;◼]
A[[i,j],:]=Q'*A[[i,j],:] # Q' acts like a square matrix even though Q was "thin"
end
i==n && return A
Q=qrQ(A[i,i+1]') # We really need an LQ
A[:,i+1]*=Q
for j=(i+2):n
Q=qrQc(A[i,[i+1,j]]) # LQ for a "domino"=[◥;◼]'
A[:,[i+1,j]]*=Q
end
end
end
Out[269]:
In [270]:
A=block(randn(15,15))
A_after=BandBidiagonal(A)
spy(A_after)
[svdvals(unblock(A))[1:5] svdvals(unblock(A_after))[1:5]]
Out[270]:
In [271]:
A=unblock(A_after)
Q1=qrQ(A[1,2:6]')
A[1:6,2:6]*=Q1
spyt(A)
rect_red(1:1,2:6) #QR from this block
rect_blue(1:6,2:6) #QR applied to this block
Out[271]:
In [272]:
Q2=qrQ(A[2:6,2:2])
A[2:6,2:11]=Q2'*A[2:6,2:11]
spyt(A)
rect_red(2:6,2:2) #QR from this block
rect_blue(2:6,2:11) #QR applied to this block
Out[272]:
In [273]:
Q3=qrQ(A[2,7:11]')#Clear the fill-in from the previous block
A[2:11,7:11]*=Q3
spyt(A)
rect_red(2:2,7:11) #QR from this block
rect_blue(2:11,7:11) #QR applied to this block
Out[273]:
In [274]:
A=block(randn(25,25),5,5);
A=BandBidiagonal(A);
s=size(A[1,1],1) #Square block size
n=size(A,1) #Number of blocks
A=unblock(A)
i=1
Q=qrQ(A[i,i+(1:s)]') #odd
A[i+(0:s),i+(1:s)]*=Q
rect_red(i:i,i+(1:s)) #QR from this block
rect_blue(i+(0:s),i+(1:s)) #QR applied to this block
for i=1:s:((n-2)*s)
Q=qrQ(A[i+(1:s),i+1:i+1]) #even
A[i+(1:s),i+(1:2s)]=Q*A[i+(1:s),i+(1:2s)]
rect_red(i+(1:s),i+1:i+1) #QR from this block
rect_blue(i+(1:s),i+(1:2s)) #QR applied to this block
Q=qrQ(A[i+1,i+s+(1:s)]') #odd
A[i+(1:2s),i+s+(1:s)]*=Q
rect_red(i+1:i+1,i+s+(1:s)) #QR from this block
rect_blue(i+(1:2s),i+s+(1:s)) #QR applied to this block
end
i=(n-2)*s+1
Q=qrQ(A[i+(1:s),i+1:i+1]) #even
A[i+(1:s),i+1:end]=Q*A[i+(1:s),i+1:end]
rect_red(i+(1:s),i+1:i+1) #QR from this block
rect_blue(i+(1:s),i+1:(n*s)) #QR applied to this block
Q=qrQ(A[i+1,i+s+1:end]') #odd
A[i+1:end,i+s+1:end]*=Q
rect_red(i+1:i+1,i+s+1:(n*s)) #QR from this block
rect_blue(i+1:(n*s),i+s+1:(n*s)) #QR applied to this block
i=(n-1)*s+1
Q=qrQ(A[i+1:end,i+1:i+1]) #even
A[i+1:end,i+1:end]=Q*A[i+1:end,i+1:end]
rect_red(i+1:(n*s),i+1:i+1) #QR from this block
rect_blue(i+1:(n*s),i+1:(n*s)) #QR applied to this block
spyt(A)
Out[274]:
In [275]:
@pyimport matplotlib.animation as anim
function block_bidiagonalize(M,s1=5,s2=5; record_video=false, video_file="blocksvd.mp4")
if record_video
video = anim.FFMpegWriter(fps=6, extra_args=["-vcodec", "libx264", "-pix_fmt", "yuv420p"])
video[:setup](gcf(), video_file)
end
A=BandBidiagonal(block(M,s1,s2))
s=size(A[1,1],1) #Square block size
n=size(A,1) #Number of blocks
A=unblock(A)
for j=1:n*s-2 #bulge chasing: elimination on row j+1
endb=min(j+s,n*s)
Q=qrQ(A[j,j+1:endb]') #xGBCW1
A[j:endb,j+1:endb]*=Q
spyt(A)
rect_red(j:j,j+1:endb) #QR from this block
rect_blue(j:endb,j+1:endb) #QR applied to this block
record_video && video[:grab_frame]()
lastb=((n-floor(j/s)-1)*s)+j #index of last block
for i=j:s:lastb
endb, endbp1=min(i+s,n*s), min(i+2s,n*s) #index of end of block and its neighbor
Q=qrQ(A[i+1:endb,i+1:i+1]) #xGBCW2
A[i+1:endb,i+1:endbp1]=Q*A[i+1:endb,i+1:endbp1]
spyt(A)
rect_red(i+1:endb,i+1:i+1) #QR from this block
rect_blue(i+1:endb,i+1:endbp1) #QR applied to this block
record_video && video[:grab_frame]()
i==lastb && break
Q=qrQ(A[i+1,i+s+1:endbp1]') #xGBCW3
A[i+1:endbp1,i+s+1:endbp1]*=Q
spyt(A)
rect_red(i+1:i+1,i+s+1:endbp1) #QR from this block
rect_blue(i+1:endbp1,i+s+1:endbp1) #QR applied to this block
record_video && video[:grab_frame]()
end
#Gray out stuff
clf()
endb=min(j+s,n*s)
rect_gray(j:j,j+1:endb)
rect_gray(j:endb,j+1:endb)
for i=j:s:lastb
endb, endbp1=min(i+s,n*s), min(i+2s,n*s)
rect_gray(i+1:endb,i+1:i+1)
rect_gray(i+1:endb,i+1:endbp1)
rect_gray(i+1:i+1,i+s+1:endbp1)
rect_gray(i+1:endbp1,i+s+1:endbp1)
end
end
clf()
spyt(A)
if record_video
video[:grab_frame]()
video[:finish]()
end
A
end
Out[275]:
In [276]:
function block_svdvals(M,s1=5,s2=5)
A=block_bidiagonalize(M,s1,s2)
B=Bidiagonal(diag(A), diag(A,1), true)
svdvals(B)
end
[block_svdvals(M) svdvals(M)]
Out[276]:
In [277]:
M=randn(25,25)
A=block_bidiagonalize(M,5,5)
spyt(A) #imshow(A, cmap="bwr")
Out[277]:
In [285]:
;ls -l *.mp4
embed_video(filename)=display("text/html", string("""<video autoplay controls><source src="data:video/x-m4v;base64,""",
base64(open(readbytes,filename)),"""" type="video/mp4"></video>"""))
embed_video("blocksvd.mp4")
In [ ]: